Good testing is the most important part of software development. It does not matter how beautiful, or fast, or high-tech your application is, if it crashes, you leave a poor impression and, crucially, your customer can lose valuable data.
Manual testing of the application as an end user is not a viable way to test, because the real problem is not the code you have written, but the existing code. Usually you test the code you have just written, but you do not retest all the already existing code in your application. And you know that when you touch any part of the code you can break any other part of the application.
While making any changes to your code, you want to handle it with the immense responsibility to keep the application from breaking. The best way to accomplish this is by using automatic testing. We are going to use automatic testing by means of JUnit.
JUnit
JUnit is a popular tool for doing automated testing. This tool comes integrated with Eclipse, so you do not need to download it. OpenXava extends the capacities of JUnit, allowing you to test an OpenXava module exactly in the same way an end user would. In fact, OpenXava uses HtmlUnit, software that simulates a real browser (including JavaScript) from Java. All this is available from the OpenXava class ModuleTestBase. It allows you to automate the test you would do by hand using a real browser in a simple way.
The best way to understand testing in OpenXava is to see it in action.
ModuleTestBase for testing the modules
The way to create a test for an OpenXava module is by extending the ModuleTestBase class from org.openxava.tests package. It connects to the OpenXava module as a real browser, and has a lot of methods that allows you to test your module. Let's create the test for your Customer module.
The code for the test
Create a new package named org.openxava.invoicing.tests and then create a new class named CustomerTest inside it with the next code:
packageorg.openxava.invoicing.tests;importorg.openxava.tests.*;publicclass CustomerTest extends ModuleTestBase {// Must extend from ModuleTestBasepublic CustomerTest(String testName){super(testName, "Invoicing", // We indicate the application name (Invoicing)"Customer");// and the module name (Customer)}// The test methods must start with 'test'publicvoid testCreateReadUpdateDelete()throwsException{
login("admin", "admin");// The user sign in to access the module// Create
execute("CRUD.new");// Clicks on 'New' button
setValue("number", "77");// Types 77 as the value for the 'number' field
setValue("name", "JUNIT Customer");// Sets the value for the 'name' field
setValue("address.street", "JUNIT Street");// Note the dot notation// to access a reference member
setValue("address.zipCode", "77555");// Etc
setValue("address.city", "The JUNIT city");// Etc
setValue("address.state", "The JUNIT state");// Etc
execute("CRUD.save");// Clicks on 'Save' button
assertNoErrors();// Verifies that the application does not show errors
assertValue("number", "");// Verifies the 'number' field is empty
assertValue("name", "");// Verifies the 'name' field is empty
assertValue("address.street", "");// Etc
assertValue("address.zipCode", "");// Etc
assertValue("address.city", "");// Etc
assertValue("address.state", "");// Etc// Read
setValue("number", "77");// Types 77 as the value for the 'number' field
execute("CRUD.refresh");// Clicks on 'Refresh' button
assertValue("number", "77");// Verifies the 'number' field has 77
assertValue("name", "JUNIT Customer");// and 'name' has 'JUNIT Customer'
assertValue("address.street", "JUNIT Street");// Etc
assertValue("address.zipCode", "77555");// Etc
assertValue("address.city", "The JUNIT city");// Etc
assertValue("address.state", "The JUNIT state");// Etc// Update
setValue("name", "JUNIT Customer MODIFIED");// Changes the value// of 'name' field
execute("CRUD.save");// Clicks on 'Save' button
assertNoErrors();// Verifies that the application does not show errors
assertValue("number", "");// Verifies the 'number' field is empty
assertValue("name", "");// Verifies the 'name' field is empty// Verify if modified
setValue("number", "77");// Types 77 as the value for 'number' field
execute("CRUD.refresh");// Clicks on 'Refresh' button
assertValue("number", "77");// Verifies the 'number' field has 77
assertValue("name", "JUNIT Customer MODIFIED");// and 'name'// has 'JUNIT Customer MODIFIED'// Delete
execute("CRUD.delete");// Clicks on 'Delete' button
assertMessage("Customer deleted successfully");// Verifies that the// message 'Customer deleted successfully' is shown to the user}}
This test creates a new customer, searches it, modifies it, and finally deletes it. You see how you can use methods such as execute() or setValue() for simulating user actions, and the methods like assertValue(), assertNoErrors() or assertMessage() to verify the state of the user interface. Your test acts as the hands and eyes of the user:
In execute() you must specify the qualified name of the action, that means ControllerName.actionName. How can you know it? Simple, put your mouse over an action link, and you will see in the status bar of your browser a JavaScript code that includes the qualified action name:
Now, you know how to create a test for testing the basic CRUD operations of a module. It's not required to write an exhaustive test at first. Just test the basic things, those that you usually test using the browser. Your test will naturally grows with your application and as the user feedback grows.
Let's learn how to execute your test from Eclipse.
Executing the tests from Eclipse
As mentioned earlier, JUnit is integrated into Eclipse, so running your tests from Eclipse is plain vanilla. Just put your mouse over the test class, and with the right button choose Run As > JUnit Test:
If the test does not pass, the bar will be red. You can try it. Edit the CustomerTest and comment the line that sets the value for the name field:
...
setValue("number", "77");// setValue("name", "JUNIT Customer"); // Comment this line
setValue("address.street", "JUNIT Street");
...
Now, rerun the test. Since name is a required property, an error message will be shown to the user, and the object will not be saved:
The failed assert is assertNoErrors(), which in addition to failure shows the error on the console. So, in the execution console of your test you will see the message like this one:
16-jul-2009 18:03 org.openxava.tests.ModuleTestBase assertNoMessages
SEVERE: Error unexpected: Value for Name in Customer is required
The problem is clear. The customer is not saved because the name is required, and it's not specified.
You have seen how the test behaves when it fails. Now, you can uncomment back the guilty line and run the test again to verify that everything is OK.
Creating test data using JPA
In your first test, CustomerTest, the test itself starts creating the data that is used in the rest of the test. This is a good way to go, especially if you want to test the data entry functionality too. But, if you want to test only a small buggy case, or your module simply does not allow adding new objects, you can create the data you need for testing using JPA from your test.
Using setUp() and tearDown() methods
We are going to use ProductTest to learn how to use JPA for creating test data. We'll create some products before each test execution and remove them afterwards. Let's see the code for ProductTest:
packageorg.openxava.invoicing.tests;importjava.math.*;importorg.openxava.invoicing.model.*;importorg.openxava.tests.*;importstaticorg.openxava.jpa.XPersistence.*;publicclass ProductTest extends ModuleTestBase {private Author author;// We declare the entities to be createdprivate Category category;// as instance members in orderprivate Product product1;// to be available from inside any test methodprivate Product product2;// and to be removed at the end of each testpublic ProductTest(String testName){super(testName, "Invoicing", "Product");}protectedvoid setUp()throwsException{// setUp() is always executed// before each testsuper.setUp();// It's needed because ModuleTestBase uses it for// initializing, even JPA is initialized here.
createProducts();// Creates the data used in the tests}protectedvoid tearDown()throwsException{// tearDown() is always// executed after each testsuper.tearDown();// It's needed, ModuleTestBase closes resources here
removeProducts();// The data used for testing is removed}publicvoid testRemoveFromList()throwsException{ ... }publicvoid testChangePrice()throwsException{ ... }privatevoid createProducts(){ ... }privatevoid removeProducts(){ ... }}
Here we are overwriting the setUp() and tearDown() methods. These methods are JUnit methods that are executed just before and after each test execution. We create the testing data before each test execution, and remove the data after each test execution. Thus, each test can rely on the precise data to be executed. It does not matter if some other test removes or modifies the data, or the execution order of the test. Always, at the beginning of each test we have all the data ready to use.
Creating data with JPA
The createProducts() method is responsible for creating the test data using JPA. Let's examine it:
privatevoid createProducts(){// Creating the Java objects
author = new Author();// Regular Java objects are created
author.setName("JUNIT Author");// We use setters just as in plain Java
category = new Category();
category.setDescription("JUNIT Category");
product1 = new Product();
product1.setNumber(900000001);
product1.setDescription("JUNIT Product 1");
product1.setAuthor(author);
product1.setCategory(category);
product1.setPrice(newBigDecimal("10"));
product2 = new Product();
product2.setNumber(900000002);
product2.setDescription("JUNIT Product 2");
product2.setAuthor(author);
product2.setCategory(category);
product2.setPrice(newBigDecimal("20"));// Marking as persistent objects
getManager().persist(author);// getManager() is from XPersistence
getManager().persist(category);// persist() marks the object as persistent
getManager().persist(product1);// so it will be saved to the database
getManager().persist(product2);// Commit changes to the database
commit();// commit() is from XPersistence. It saves all object to the database// and commits the transaction}
As you can see, first you create the Java objects in the Java conventional way. Note that you assign it to instance members. Thus you can use it inside tests. Then, you mark them as persistent, using the persist() method of the JPA EntityManager. To obtain the EntityManager you only have to write getManager() because you have the static import above:
importstaticorg.openxava.jpa.XPersistence.*;
...
getManager().persist(author);// Thanks to the XPersistence static import it's the same as
XPersistence.getManager().persist(author);
...
commit();// Thanks to the XPersistence static import it's the same as
XPersistence.commit();
To finalize, commit() (also from XPersistence) saves all the data from objects to database and then commits the transaction. After that, the data is in the database ready to be used by your test.
Removing data with JPA
After the test is executed we remove the test data in order to leave the database clean. This is done by the removeProducts() method:
privatevoid removeProducts(){// Called from tearDown() so it's executed// after each test
remove(product1, product2, author, category);// remove() removes
commit();// Commits the changes to database, in this case deleting data}privatevoid remove(Object ... entities){// Using varargs argumentfor(Object entity : entities){// Iterating for all arguments
getManager().remove(getManager().merge(entity));// Removing(1)}}
It's a simple loop to remove all the entities used in the test. To remove an entity in JPA you have to use the remove() method, but in this case you have to use the merge() method too (shown as 1). This is because you cannot remove a detached entity. When you use commit() in createProducts() all saved entities become detached entities. This is because they continue being valid Java object but the persistent context (the union between entities and database) has been lost on commit(), so you must reattach them to the new persistent context. This concept is easy to understand seeing the next code:
getManager().persist(author);// author is attached to the current persistence context
commit();// The current persistence context is over, so author becomes detached
getManager().remove(author);// It fails because author is detached
author = getManager().merge(author);// Reattaches author to the current context
getManager().remove(author);// It works
Apart from this curious detail about merge(), the code for removing is quite simple.
Filtering data from list mode in a test
Now that you know how to create and remove the data for the tests, let's examine the test methods for your Product module. The first one is testRemoveFromList() that checks a row in list mode and clicks on “Delete selected” button. Let's see the code:
publicvoid testRemoveFromList()throwsException{
login("admin", "admin");
setConditionValues("", "JUNIT");// Put the values for filtering data
setConditionComparators("=", "contains_comparator");// Put the comparators for filtering data
execute("List.filter");// Clicks on filter button
assertListRowCount(2);// Verifies that there are 2 rows
checkRow(1);// We select row 1 (really the second one)
execute("CRUD.deleteSelected");// Clicks on the delete button
assertListRowCount(1);// Verifies that now there is only 1 row}
Here we filter in list mode all products that contain the “JUNIT” word (remember that you have created two of them in createProducts() method), then we verify that there are two rows, select the second product, and remove it, verifying at the end that one product remains in the list.
You have learned how to select a row (using checkRow()) and how to assert the row count (using assertListRowCount()). The trickiest part might be filtering the list using setConditionValues() and setConditionComparators(). Both methods receive a variable number of string arguments with values and comparators for the condition, just as shown here:
The values are assigned to the list filter user interface sequentially (from left to right). In this case there are two values, but you can use all you need. You do not need to specify all values. The setConditionValues() method accepts any string value whereas setConditionComparators() accepts the next possible values: starts_comparator, contains_comparator, =, <>, >=, <=, >,<, in_comparator, not_in_comparator and range_comparator.
Using entity instances inside a test
The remaining test, testChangePrice(), simply chooses a product and changes its price. We are going to use it in an entity created in createProducts():
The only new thing in this test is to provide a value to number to search the product. We get the value using product1.getNumber() (shown as 1). Remember that product1 is an instance variable of the test that is populated in createProducts(), which is called from setUp() so it is executed before each test.
You have a test class for Product and at the same time you have learned testing with test data created using JPA. Run it, it should be green.
Using existing data for testing
Sometimes you can simplify the test by relying on a test database which is populated with the data needed for testing. If you do not want to test data creation from the module itself, and you do not remove data in the test, this can be a good option.
For example, you can test Author and Category with a simple test like this one:
packageorg.openxava.invoicing.tests;importorg.openxava.tests.*;publicclass AuthorTest extends ModuleTestBase {public AuthorTest(String testName){super(testName, "Invoicing", "Author");}publicvoid testReadAuthor()throwsException{
login("admin", "admin");
assertValueInList(0, 0, "JAVIER CORCOBADO");// The first author// in the list is JAVIER CORCOBADO
execute("Mode.detailAndFirst");// On change detail mode the// the first object in list is displayed
assertValue("name", "JAVIER CORCOBADO");
assertCollectionRowCount("products", 2);// It has 2 products
assertValueInCollection("products", 0, // Row 0 of products"number", "2");// has “2” in “number” column
assertValueInCollection("products", 0, "description", "Arco iris de lágrimas");
assertValueInCollection("products", 1, "number", "3");
assertValueInCollection("products", 1, "description", "Ritmo de sangre");}}
This test verifies that the first author in the list is “JAVIER CORCOBADO”, remember to create it before execute the test. It goes to the detail and asserts that it has a collection called products with 2 products: “Arco iris de lágrimas” and “Ritmo de sangre”, before executing the test create them and associate them to "JAVIER CORCOBADO". By the way, now you have learned how to use assertValueInList(), assertValueInCollection() and assertCollectionRowCount() methods.
We can use the same technique to test the Category module:
In this case we see that in the list the first three categories are “MUSIC”, “BOOKS” and “SOFTWARE”. Remember to create them before executing the test.
You have seen how the technique of using pre-existing data from a test database allows you to create simpler tests. Starting from a simple test and further complicating it on demand is a good way to go. Remember to add the corresponding data using the modules before executing these tests.
Testing collections
Now it's time to face the test for the main module of your application, the InvoiceTest. As of now the functionality of the Invoice module is limited. You can only add, remove and modify invoices. Even so, this is a big test. It contains a collection, so you will learn here how to test collections.
Breaking down tests in several methods
The test for creating an Invoice is broken down into several methods:
packageorg.openxava.invoicing.tests;importjava.text.*;importjava.util.*;importjavax.persistence.*;importorg.openxava.tests.*;importorg.openxava.util.*;importstaticorg.openxava.jpa.XPersistence.*;// To use JPApublicclass InvoiceTest extends ModuleTestBase {privateString number;// To store the number of the tested invoicepublic InvoiceTest(String testName){super(testName, "Invoicing", "Invoice");}publicvoid testCreate()throwsException{// The test method
login("admin", "admin");
verifyDefaultValues();
chooseCustomer();
addDetails();
setOtherProperties();
save();
verifyCreated();
remove();}privatevoid verifyDefaultValues()throwsException{ … }privatevoid chooseCustomer()throwsException{ … }privatevoid addDetails()throwsException{ … }privatevoid setOtherProperties()throwsException{ … }privatevoid save()throwsException{ … }privatevoid verifyCreated()throwsException{ … }privatevoid remove()throwsException{ … }privateString getCurrentYear(){ … }privateString getCurrentDate(){ … }privateString getNumber(){ … }}
The only test method in this class is testCreate(), but because this test is somewhat large, it is better to break it down into several shorter methods. In fact, it's a good object-oriented practice to write short methods.
Because this method is short you can see in a glance what it does. In this case the method verifies the default values for a new invoice, chooses a customer, adds the details, adds other properties, saves the invoice, verifies that it is correctly saved and finally deletes it. Let's dip into the details of these steps.
Asserting default values
First, it verifies whether the default values for a new invoice are correctly calculated or not. This is done by the verifyDefaultValues() method:
When the user clicks on “New”, the year, number and date field must be prefilled with valid data. The verifyDefaultValues() method tests this. It uses several utility methods to calculate the expected values:
privateString getCurrentYear(){// Current year in string formatreturnnewSimpleDateFormat("yyyy").format(newDate());// The standard// way to do it with Java}privateString getCurrentDate(){// Current date as string in short formatreturnDateFormat.getDateInstance(// The standard way to do it with JavaDateFormat.SHORT).format(newDate());}privateString getNumber(){// The invoice number for a new invoiceif(number == null){// We use lazy initializationQuery query = getManager(). // A JPA query to get the last number
createQuery("select max(i.number) from Invoice i where i.year = :year");
query.setParameter("year", Dates.getYear(newDate()));// Dates is an// OpenXava utilityInteger lastNumber = (Integer) query.getSingleResult();if(lastNumber == null) lastNumber = 0;
number = Integer.toString(lastNumber + 1);// Adding 1 to the last invoice number}return number;}
The getCurrentYear() and getCurrentDate() methods use classic Java techniques to format the date as String.
The getNumber() method, on the other hand, is a little more complex. It uses JPA to calculate the last invoice number of the current year, then return this value plus one. Due to its access to the database it is heavier than a simple Java calculation, therefore we use lazy initialization. Lazy initialization delays the calculation until the first time it is needed, and stores it for future use. We do it by saving the value in the number field.
Note the usage of the Dates class to extract the year from the date. Dates is a utility class you can find in org.openxava.util.
Data entry
Now it's time for the chooseCustomer() method of the invoice:
privatevoid chooseCustomer()throwsException{
setValue("customer.number", "1");
assertValue("customer.name", "JAVIER PANIZA");// The customer 1 should exist in DB}
Upon entry of the customer number the customer name is simply filled with the appropriate value. Since the test relies on customer 1 with name "JAVIER PANIZA" existing already, you should create it before running the test. With this the customer 1 is associated to the current invoice.
And now comes the most tricky part of the test: adding the detail lines:
privatevoid addDetails()throwsException{
assertCollectionRowCount("details", 0);// The collection is empty// Adding a detail line
setValueInCollection("details", 0, // 0 means the first row"product.number", "1");
assertValueInCollection("details", 0,
"product.description", "Peopleware: Productive Projects and Teams");
setValueInCollection("details", 0, "quantity", "2");// Adding another detail
setValueInCollection("details", 1, "product.number", "2");
assertValueInCollection("details", 1, "product.description", "Arco iris de lágrimas");
setValueInCollection("details", 1, "quantity", "1");
assertCollectionRowCount("details", 2);// Now we have 2 rows}
Testing a collection is the same as testing any other part of your application. You have to follow the same steps as an end user with a browser. You have methods such as setValueInCollection(), assertValueInCollection() or assertCollectionRowCount() to work with collections. Note how these methods have the collection name as first argument, and some of them receive the row number with 0 based index. Remember to create the product 1 and 2 with the corresponding descriptions before executing this test.
Now that we have the details added, we are going to fill the remaining data and save the invoice. The remaining data is set by setOtherProperties() method:
privatevoid setOtherProperties()throwsException{
setValue("remarks", "This is a JUNIT test");}
Here we give a value to the remarks field. Now we are ready to save the invoice:
It simply clicks on “Save”, then verifies for any errors and makes sure that the view is clean.
Verifying the data
Now, we will search the newly created invoice to verify that it has been saved correctly. This is done by the verifyCreated() method:
privatevoid verifyCreated()throwsException{
execute("CRUD.search");// Show the dialog to search
setValue("year", getCurrentYear());// The current year to year field
setValue("number", getNumber());// The invoice number of the test
execute("Search.search");// Load the invoice back from the database// In the rest of the test we assert that the values are the correct ones
assertValue("year", getCurrentYear());
assertValue("number", getNumber());
assertValue("date", getCurrentDate());
assertValue("customer.number", "1");
assertValue("customer.name", "JAVIER PANIZA");
assertCollectionRowCount("details", 2);// Row 0
assertValueInCollection("details", 0, "product.number", "1");
assertValueInCollection("details", 0, "product.description",
"Peopleware: Productive Projects and Teams");
assertValueInCollection("details", 0, "quantity", "2");// Row 1
assertValueInCollection("details", 1, "product.number", "2");
assertValueInCollection("details", 1, "product.description",
"Arco iris de lágrimas");
assertValueInCollection("details", 1, "quantity", "1");
assertValue("remarks", "This is a JUNIT test");}
After searching the created invoice we verify whether the values we have saved are there. If the test reaches this point your Invoice module works fine. The only thing remaining is to delete the created invoice so that the test can be
executed again. We do that in the remove() method:
It just clicks on “Delete” and verifies that no errors are produced.
Congratulations! You have your InvoiceTest completed. Execute it, it should be green; if not revise the data in the database, maybe you have to add the corresponding products, customer, etc.
Suite
You have 5 test cases to preserve the quality of your application. When you finish some enhancement or fix of your application you must execute all your tests to ensure that your already existing functionality is not broken.
Traditionally, to execute all the test for your application you have to create a test suite, and execute it. A test suite is a class that aggregates all your JUnit tests so you can execute them all at once. Fortunately, if you are working with Eclipse you do not need to write a test suite class, Eclipse allows you to execute all the test for your application automatically:
That is, if you execute Run As > JUnit Test on the project then all its JUnit tests are executed.
Summary
You have automated the tests for all the current functionality of your application. This test code seems to be more verbose and boring than the real application code. But remember, the test code is the most valuable asset you have. Right now you may not believe me, but try to do tests and once they save your life, you will not develop without test code any more.
What to test? Don't do an exhaustive test at first. It's better to test a little than to test nothing. If you try to do exhaustive testing you will end up testing nothing. Start doing a little testing of all your code, and with any new feature or fix also write the test for it. In the end, you will have a very powerful test suite. Test little but test always.
In fact, testing is an on going task. In order to preach with the example, from now on we'll write all the tests for the code we will develop in the rest of the book. Thus you will learn more tips about testing in the next lessons.
Table of Contents
Lesson 3: Automated testing
Good testing is the most important part of software development. It does not matter how beautiful, or fast, or high-tech your application is, if it crashes, you leave a poor impression and, crucially, your customer can lose valuable data.Manual testing of the application as an end user is not a viable way to test, because the real problem is not the code you have written, but the existing code. Usually you test the code you have just written, but you do not retest all the already existing code in your application. And you know that when you touch any part of the code you can break any other part of the application.
While making any changes to your code, you want to handle it with the immense responsibility to keep the application from breaking. The best way to accomplish this is by using automatic testing. We are going to use automatic testing by means of JUnit.
JUnit
JUnit is a popular tool for doing automated testing. This tool comes integrated with Eclipse, so you do not need to download it. OpenXava extends the capacities of JUnit, allowing you to test an OpenXava module exactly in the same way an end user would. In fact, OpenXava uses HtmlUnit, software that simulates a real browser (including JavaScript) from Java. All this is available from the OpenXava class ModuleTestBase. It allows you to automate the test you would do by hand using a real browser in a simple way.The best way to understand testing in OpenXava is to see it in action.
ModuleTestBase for testing the modules
The way to create a test for an OpenXava module is by extending the ModuleTestBase class from org.openxava.tests package. It connects to the OpenXava module as a real browser, and has a lot of methods that allows you to test your module. Let's create the test for your Customer module.The code for the test
Create a new package named org.openxava.invoicing.tests and then create a new class named CustomerTest inside it with the next code:This test creates a new customer, searches it, modifies it, and finally deletes it. You see how you can use methods such as execute() or setValue() for simulating user actions, and the methods like assertValue(), assertNoErrors() or assertMessage() to verify the state of the user interface. Your test acts as the hands and eyes of the user:
In execute() you must specify the qualified name of the action, that means ControllerName.actionName. How can you know it? Simple, put your mouse over an action link, and you will see in the status bar of your browser a JavaScript code that includes the qualified action name:
Now, you know how to create a test for testing the basic CRUD operations of a module. It's not required to write an exhaustive test at first. Just test the basic things, those that you usually test using the browser. Your test will naturally grows with your application and as the user feedback grows.
Let's learn how to execute your test from Eclipse.
Executing the tests from Eclipse
As mentioned earlier, JUnit is integrated into Eclipse, so running your tests from Eclipse is plain vanilla. Just put your mouse over the test class, and with the right button choose Run As > JUnit Test:If the test does not pass, the bar will be red. You can try it. Edit the CustomerTest and comment the line that sets the value for the name field:
Now, rerun the test. Since name is a required property, an error message will be shown to the user, and the object will not be saved:
The failed assert is assertNoErrors(), which in addition to failure shows the error on the console. So, in the execution console of your test you will see the message like this one:
The problem is clear. The customer is not saved because the name is required, and it's not specified.
You have seen how the test behaves when it fails. Now, you can uncomment back the guilty line and run the test again to verify that everything is OK.
Creating test data using JPA
In your first test, CustomerTest, the test itself starts creating the data that is used in the rest of the test. This is a good way to go, especially if you want to test the data entry functionality too. But, if you want to test only a small buggy case, or your module simply does not allow adding new objects, you can create the data you need for testing using JPA from your test.Using setUp() and tearDown() methods
We are going to use ProductTest to learn how to use JPA for creating test data. We'll create some products before each test execution and remove them afterwards. Let's see the code for ProductTest:Here we are overwriting the setUp() and tearDown() methods. These methods are JUnit methods that are executed just before and after each test execution. We create the testing data before each test execution, and remove the data after each test execution. Thus, each test can rely on the precise data to be executed. It does not matter if some other test removes or modifies the data, or the execution order of the test. Always, at the beginning of each test we have all the data ready to use.
Creating data with JPA
The createProducts() method is responsible for creating the test data using JPA. Let's examine it:As you can see, first you create the Java objects in the Java conventional way. Note that you assign it to instance members. Thus you can use it inside tests. Then, you mark them as persistent, using the persist() method of the JPA EntityManager. To obtain the EntityManager you only have to write getManager() because you have the static import above:
To finalize, commit() (also from XPersistence) saves all the data from objects to database and then commits the transaction. After that, the data is in the database ready to be used by your test.
Removing data with JPA
After the test is executed we remove the test data in order to leave the database clean. This is done by the removeProducts() method:It's a simple loop to remove all the entities used in the test. To remove an entity in JPA you have to use the remove() method, but in this case you have to use the merge() method too (shown as 1). This is because you cannot remove a detached entity. When you use commit() in createProducts() all saved entities become detached entities. This is because they continue being valid Java object but the persistent context (the union between entities and database) has been lost on commit(), so you must reattach them to the new persistent context. This concept is easy to understand seeing the next code:
Apart from this curious detail about merge(), the code for removing is quite simple.
Filtering data from list mode in a test
Now that you know how to create and remove the data for the tests, let's examine the test methods for your Product module. The first one is testRemoveFromList() that checks a row in list mode and clicks on “Delete selected” button. Let's see the code:Here we filter in list mode all products that contain the “JUNIT” word (remember that you have created two of them in createProducts() method), then we verify that there are two rows, select the second product, and remove it, verifying at the end that one product remains in the list.
You have learned how to select a row (using checkRow()) and how to assert the row count (using assertListRowCount()). The trickiest part might be filtering the list using setConditionValues() and setConditionComparators(). Both methods receive a variable number of string arguments with values and comparators for the condition, just as shown here:
The values are assigned to the list filter user interface sequentially (from left to right). In this case there are two values, but you can use all you need. You do not need to specify all values. The setConditionValues() method accepts any string value whereas setConditionComparators() accepts the next possible values: starts_comparator, contains_comparator, =, <>, >=, <=, >, <, in_comparator, not_in_comparator and range_comparator.
Using entity instances inside a test
The remaining test, testChangePrice(), simply chooses a product and changes its price. We are going to use it in an entity created in createProducts():The only new thing in this test is to provide a value to number to search the product. We get the value using product1.getNumber() (shown as 1). Remember that product1 is an instance variable of the test that is populated in createProducts(), which is called from setUp() so it is executed before each test.
You have a test class for Product and at the same time you have learned testing with test data created using JPA. Run it, it should be green.
Using existing data for testing
Sometimes you can simplify the test by relying on a test database which is populated with the data needed for testing. If you do not want to test data creation from the module itself, and you do not remove data in the test, this can be a good option.For example, you can test Author and Category with a simple test like this one:
This test verifies that the first author in the list is “JAVIER CORCOBADO”, remember to create it before execute the test. It goes to the detail and asserts that it has a collection called products with 2 products: “Arco iris de lágrimas” and “Ritmo de sangre”, before executing the test create them and associate them to "JAVIER CORCOBADO". By the way, now you have learned how to use assertValueInList(), assertValueInCollection() and assertCollectionRowCount() methods.
We can use the same technique to test the Category module:
In this case we see that in the list the first three categories are “MUSIC”, “BOOKS” and “SOFTWARE”. Remember to create them before executing the test.
You have seen how the technique of using pre-existing data from a test database allows you to create simpler tests. Starting from a simple test and further complicating it on demand is a good way to go. Remember to add the corresponding data using the modules before executing these tests.
Testing collections
Now it's time to face the test for the main module of your application, the InvoiceTest. As of now the functionality of the Invoice module is limited. You can only add, remove and modify invoices. Even so, this is a big test. It contains a collection, so you will learn here how to test collections.Breaking down tests in several methods
The test for creating an Invoice is broken down into several methods:The only test method in this class is testCreate(), but because this test is somewhat large, it is better to break it down into several shorter methods. In fact, it's a good object-oriented practice to write short methods.
Because this method is short you can see in a glance what it does. In this case the method verifies the default values for a new invoice, chooses a customer, adds the details, adds other properties, saves the invoice, verifies that it is correctly saved and finally deletes it. Let's dip into the details of these steps.
Asserting default values
First, it verifies whether the default values for a new invoice are correctly calculated or not. This is done by the verifyDefaultValues() method:When the user clicks on “New”, the year, number and date field must be prefilled with valid data. The verifyDefaultValues() method tests this. It uses several utility methods to calculate the expected values:
The getCurrentYear() and getCurrentDate() methods use classic Java techniques to format the date as String.
The getNumber() method, on the other hand, is a little more complex. It uses JPA to calculate the last invoice number of the current year, then return this value plus one. Due to its access to the database it is heavier than a simple Java calculation, therefore we use lazy initialization. Lazy initialization delays the calculation until the first time it is needed, and stores it for future use. We do it by saving the value in the number field.
Note the usage of the Dates class to extract the year from the date. Dates is a utility class you can find in org.openxava.util.
Data entry
Now it's time for the chooseCustomer() method of the invoice:Upon entry of the customer number the customer name is simply filled with the appropriate value. Since the test relies on customer 1 with name "JAVIER PANIZA" existing already, you should create it before running the test. With this the customer 1 is associated to the current invoice.
And now comes the most tricky part of the test: adding the detail lines:
Testing a collection is the same as testing any other part of your application. You have to follow the same steps as an end user with a browser. You have methods such as setValueInCollection(), assertValueInCollection() or assertCollectionRowCount() to work with collections. Note how these methods have the collection name as first argument, and some of them receive the row number with 0 based index. Remember to create the product 1 and 2 with the corresponding descriptions before executing this test.
Now that we have the details added, we are going to fill the remaining data and save the invoice. The remaining data is set by setOtherProperties() method:
Here we give a value to the remarks field. Now we are ready to save the invoice:
It simply clicks on “Save”, then verifies for any errors and makes sure that the view is clean.
Verifying the data
Now, we will search the newly created invoice to verify that it has been saved correctly. This is done by the verifyCreated() method:After searching the created invoice we verify whether the values we have saved are there. If the test reaches this point your Invoice module works fine. The only thing remaining is to delete the created invoice so that the test can be
executed again. We do that in the remove() method:
It just clicks on “Delete” and verifies that no errors are produced.
Congratulations! You have your InvoiceTest completed. Execute it, it should be green; if not revise the data in the database, maybe you have to add the corresponding products, customer, etc.
Suite
You have 5 test cases to preserve the quality of your application. When you finish some enhancement or fix of your application you must execute all your tests to ensure that your already existing functionality is not broken.Traditionally, to execute all the test for your application you have to create a test suite, and execute it. A test suite is a class that aggregates all your JUnit tests so you can execute them all at once. Fortunately, if you are working with Eclipse you do not need to write a test suite class, Eclipse allows you to execute all the test for your application automatically:
That is, if you execute Run As > JUnit Test on the project then all its JUnit tests are executed.
Summary
You have automated the tests for all the current functionality of your application. This test code seems to be more verbose and boring than the real application code. But remember, the test code is the most valuable asset you have. Right now you may not believe me, but try to do tests and once they save your life, you will not develop without test code any more.What to test? Don't do an exhaustive test at first. It's better to test a little than to test nothing. If you try to do exhaustive testing you will end up testing nothing. Start doing a little testing of all your code, and with any new feature or fix also write the test for it. In the end, you will have a very powerful test suite. Test little but test always.
In fact, testing is an on going task. In order to preach with the example, from now on we'll write all the tests for the code we will develop in the rest of the book. Thus you will learn more tips about testing in the next lessons.
Download source code of this lesson
Any problem with this lesson? Ask in the forum Everything fine? Go to Lesson 4